﻿using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using SPCAF.Sdk;
using SPCAF.Sdk.Model;
using SPCAF.Sdk.Model.Extensions;
using SPCAFContrib.Common;
using SPCAFContrib.Entities;
using SPCAFContrib.Entities.Consts;
using SPCAFContrib.Extensions;
using SPCAFContrib.Groups;
using SPCAFContrib.Rules.Code.Base;

namespace SPCAFContrib.Rules.Code
{
    [RuleMetadata(typeof(ContribCorrectnessGroup),
     CheckId = CheckIDs.Rules.Assembly.SendMailFromWcfService,
     Help = CheckIDs.Rules.Assembly.SendMailFromWcfService_HelpUrl,

     Message = "Send an e-mail from a WCF service when SPContext is not available.",
     DisplayName = "Send an e-mail from a WCF service when SPContext is not available.",
     Description = "Sending an e-mail when SPContext is not available could fail.",
     Resolution = "Prevent the mail function from reading the current context by using HttpContext.Current = null.",

     DefaultSeverity = Severity.Warning,
     SharePointVersion = new[] { "12", "14", "15" },
     Links = new []
     {
         "SPUtility.SendEmail method",
         "http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.utilities.sputility.sendemail.aspx"
     })]
    public class SendMailFromWcfService : SearchMethodRuleBase
    {
        MultiValueDictionary<string, string> MethodsAndItsSubCalls = new MultiValueDictionary<string, string>();
        
        protected override void PopulateTypeMap()
        {
            TargetTypeMap.Add(TypeKeys.SPUtility, "SendEmail");
        }

        public override void Visit(AssemblyFileReferenceWSP assembly, NotificationCollection notifications)
        {
            if (assembly.AssemblyFileReference.AssemblyDefinition == null)
                assembly.AssemblyFileReference.AssemblyDefinition = AssemblyDefinition.ReadAssembly(assembly.AssemblyLocation);
            
            if (assembly.AssemblyHasExcluded()) return;

            List<TypeDefinition> wcfServiceInterfaces =
                assembly.AssemblyFileReference.AllTypeDefinitions()
                    .Where(
                        td =>
                            td.IsInterface && td.HasCustomAttributes &&
                            td.CustomAttributes.Any(ca => ca.AttributeType.Name == "ServiceContractAttribute"))
                    .ToList();

            List<TypeDefinition> wcfServiceTypes = new List<TypeDefinition>();
            wcfServiceInterfaces.ForEach(i => wcfServiceTypes.AddRange(assembly.AssemblyFileReference.TypesThatImplementInterface(i.FullName)));

            foreach (TypeDefinition typeDefinition in wcfServiceTypes)
            {
                HashSet<string> handledMethods = new HashSet<string>();
                typeDefinition.InsideMethodCalls(MethodsAndItsSubCalls, handledMethods, true, null);
            }

            base.Visit(assembly, notifications);
        }

        protected override void OnMatch(AssemblyFileReferenceWSP assembly, CodeInstruction instruction, NotificationCollection notifications, Func<string> getNotificationMessage, Func<ElementSummary> getSummary)
        {
            Dictionary<string, List<string>> httpContextCurrent = new Dictionary<string, List<string>>();
            httpContextCurrent.Add(TypeKeys.HttpContext, new List<string>(new[] { "get_Current" }));

            if (instruction.MethodDefinition.DeclaringType != null)
            {
                TypeDefinition typeDefinition = instruction.MethodDefinition.DeclaringType;
                do
                {
                    foreach (string fullTypeName in MethodsAndItsSubCalls.Keys)
                    {
                        if (String.Equals(typeDefinition.FullName, fullTypeName) &&
                            MethodsAndItsSubCalls[fullTypeName].Contains(instruction.MethodDefinition.Name) &&
                            !instruction.MethodDefinition.HasMethodUsage(httpContextCurrent, false))
                        {
                            base.OnMatch(assembly, instruction, notifications, GetNotificationMessage, () =>
                            {
                                return GetSummary(assembly, instruction);
                            });
                            return;
                        }
                    }
                    typeDefinition = typeDefinition.DeclaringType;
                }
                while (typeDefinition != null);
            }
        }
    }
}
